/******************************************************************************
* Copyright (C) 2015 - 2021 Xilinx, Inc.  All rights reserved.
* Copyright (C) 2023 Advanced Micro Devices, Inc. All Rights Reserved.
* SPDX-License-Identifier: MIT
******************************************************************************/

/*****************************************************************************/
/**
 *
 * @file xrtcpsu.c
 * @addtogroup rtcpsu Overview
 * @{
 *
 * Functions in this file are the minimum required functions for the XRtcPsu
 * driver. See xrtcpsu.h for a detailed description of the driver.
 *
 * @notice	None.
 *
 *
 * <pre>
 * MODIFICATION HISTORY:
 *
 * Ver   Who    Date	Changes
 * ----- -----  -------- -----------------------------------------------
 * 1.00  kvn    04/21/15 First release
 * 1.1   kvn    09/25/15 Modify control register to enable battery
 *                       switching when vcc_psaux is not available.
 * 1.2          02/15/16 Corrected Calibration mask and Fractional
 *                       mask in CalculateCalibration API.
 * 1.3   vak    04/25/16 Corrected the RTC read and write time logic(cr#948833).
 * 1.5   ms     08/27/17 Fixed compilation warnings.
 *       ms     08/29/17 Updated code as per source code style.
 * 1.6	 aru	06/25/18 Modified logic to handle
 *			 the last day of month cotrrecly.(CR#1004282)
 * 1.6	 aru	06/25/18 Remove the checkpatch warnings.
 * 1.6   aru    07/11/18 Resolved cppcheck warnings.
 * 1.6   aru    07/11/18 Resolved doxygen warnings.
 * 1.6   aru    08/17/18 Resolved MISRA-C mandatory violations.(CR#1007752)
 * 1.7   sne    03/01/19 Added Versal support.
 * 1.7   sne    03/01/19 Fixed violations according to MISRAC-2012 standards
 *                       modified the code such as
 *                       No brackets to loop body,Declared the poiner param
 *                       as Pointer to const,No brackets to then/else,
 *                       Literal value requires a U suffix,Casting operation to a pointer
 *			 Array has no bounds specified,Logical conjunctions need brackets.
 * 1.8	 sg	07/13/19 Corrected calibration algorithm
 * 1.13	 ht	06/22/23 Added support for system device-tree flow.
 * </pre>
 *
 ******************************************************************************/

/***************************** Include Files *********************************/

#include "xrtcpsu.h"
#include "xrtcpsu_hw.h"

/************************** Constant Definitions *****************************/

/**************************** Type Definitions *******************************/

/***************** Macros (Inline Functions) Definitions *********************/

/************************** Variable Definitions *****************************/

static const u32 DaysInMonth[] = {31, 28, 31, 30, 31,
				  30, 31, 31, 30, 31, 30, 31
				 };

/************************** Function Prototypes ******************************/

static void XRtcPsu_StubHandler(void *CallBackRef, u32 Event);

/*****************************************************************************/
/**
 *
 * This function initializes a XRtcPsu instance/driver.
 *
 * The initialization entails:
 * - Initialize all members of the XRtcPsu structure.
 *
 * @param	InstancePtr is a pointer to the XRtcPsu instance.
 * @param	ConfigPtr points to the XRtcPsu device configuration structure.
 * @param	EffectiveAddr is the device base address in the virtual memory
 *		address space. If the address translation is not used then the
 *		physical address is passed.
 *		Unexpected errors may occur if the address mapping is changed
 *		after this function is invoked.
 *
 * @return	XST_SUCCESS always.
 *
 * @note		None.
 *
 ******************************************************************************/
s32 XRtcPsu_CfgInitialize(XRtcPsu *InstancePtr, XRtcPsu_Config *ConfigPtr,
			  UINTPTR EffectiveAddr)
{
	s32 Status;
	u32 ControlRegister;

	Xil_AssertNonvoid(InstancePtr != NULL);

	Xil_AssertNonvoid(ConfigPtr != NULL);

	/*
	 * Set some default values for instance data, don't indicate the device
	 * is ready to use until everything has been initialized successfully.
	 */
	InstancePtr->IsReady = 0U;
	InstancePtr->RtcConfig.BaseAddr = EffectiveAddr;
#ifndef SDT
	InstancePtr->RtcConfig.DeviceId = ConfigPtr->DeviceId;
#endif

	if (InstancePtr->OscillatorFreq == 0U) {
		InstancePtr->CalibrationValue = XRTC_CALIBRATION_VALUE;
		InstancePtr->OscillatorFreq = XRTC_TYPICAL_OSC_FREQ;
	}

	/* Set all handlers to stub values, let user configure this
	 * data later.
	 */
	InstancePtr->Handler = (XRtcPsu_Handler)XRtcPsu_StubHandler;

	InstancePtr->IsPeriodicAlarm = 0U;

	/* Set the calibration value in calibration register. */
	XRtcPsu_WriteReg(InstancePtr->RtcConfig.BaseAddr + XRTC_CALIB_WR_OFFSET,
			 InstancePtr->CalibrationValue);

	/*	Set the Oscillator crystal and Battery switch enable
	 *	in control register.
	 */
	ControlRegister = XRtcPsu_ReadReg(InstancePtr->RtcConfig.BaseAddr
					  + XRTC_CTL_OFFSET);
	XRtcPsu_WriteReg(InstancePtr->RtcConfig.BaseAddr + XRTC_CTL_OFFSET,
			 (ControlRegister | (u32)XRTCPSU_CRYSTAL_OSC_EN |
			  (u32)XRTC_CTL_BATTERY_EN_MASK));

	/* Clear the Interrupt Status and Disable the interrupts. */
	XRtcPsu_WriteReg(InstancePtr->RtcConfig.BaseAddr + XRTC_INT_STS_OFFSET,
			 ((u32)XRTC_INT_STS_ALRM_MASK |
			  (u32)XRTC_INT_STS_SECS_MASK));
	XRtcPsu_WriteReg(InstancePtr->RtcConfig.BaseAddr + XRTC_INT_DIS_OFFSET,
			 ((u32)XRTC_INT_DIS_ALRM_MASK |
			  (u32)XRTC_INT_DIS_SECS_MASK));

	/* Indicate the component is now ready to use. */
	InstancePtr->IsReady = XIL_COMPONENT_IS_READY;

	/* Clear TimeUpdated and CurrTimeUpdated */
	InstancePtr->TimeUpdated = (u32)0U;
	InstancePtr->CurrTimeUpdated = (u32)0U;

	Status = (s32)XST_SUCCESS;
	return Status;
}

/****************************************************************************/
/**
 *
 * This function is a stub handler that is the default handler such that if the
 * application has not set the handler when interrupts are enabled, this
 * function will be called.
 *
 * @param	CallBackRef is unused by this function.
 * @param	Event is unused by this function.
 *
 * @return	None.
 *
 * @note		None.
 *
 *****************************************************************************/
static void XRtcPsu_StubHandler(void *CallBackRef, u32 Event)
{
	(void) CallBackRef;
	(void) Event;
	/* Assert occurs always since this is a stub and should
	 * never be called
	 */
	Xil_AssertVoidAlways();
}

/****************************************************************************/
/**
 *
 * This function sets the RTC time by writing into rtc write register.
 *
 * @param	InstancePtr is a pointer to the XRtcPsu instance.
 * @param	Time that should be updated into RTC write register.
 *
 * @return	None.
 *
 * @note		None.
 *
 *****************************************************************************/
void XRtcPsu_SetTime(XRtcPsu *InstancePtr, u32 Time)
{
	/* Set the calibration value in calibration register, so that
	 * next Second is triggered exactly at 1 sec period
	 */
	XRtcPsu_WriteReg(InstancePtr->RtcConfig.BaseAddr + XRTC_CALIB_WR_OFFSET,
			 InstancePtr->CalibrationValue);
	/* clear the RTC secs interrupt from status register */
	XRtcPsu_WriteReg(InstancePtr->RtcConfig.BaseAddr + XRTC_INT_STS_OFFSET,
			 XRTC_INT_STS_SECS_MASK);
	InstancePtr->CurrTimeUpdated = (u32)0U;
	/* Update the flag before setting the time */
	InstancePtr->TimeUpdated = (u32)1U;
	/* Since RTC takes 1 sec to update the time into current
	 * time register, write
	 * load time + 1sec into the set time register.
	 */
	XRtcPsu_WriteSetTime(InstancePtr, Time + (u32)1U);
}

/****************************************************************************/
/**
 *
 * This function gets the current RTC time.
 *
 * @param	InstancePtr is a pointer to the XRtcPsu instance.
 *
 * @return	RTC Current time.
 *
 * @note		None.
 *
 *****************************************************************************/
u32 XRtcPsu_GetCurrentTime(XRtcPsu *InstancePtr)
{
	u32 Status;
	u32 IntMask;
	u32 CurrTime;

	IntMask = XRtcPsu_ReadReg(InstancePtr->RtcConfig.BaseAddr +
				  XRTC_INT_MSK_OFFSET);

	if ((IntMask & XRTC_INT_STS_SECS_MASK) != (u32)0) {
		/* We come here if interrupts are disabled */
		Status = XRtcPsu_ReadReg(InstancePtr->RtcConfig.BaseAddr +
					 XRTC_INT_STS_OFFSET);
		if ((InstancePtr->TimeUpdated == (u32)1) &&
		    ((Status & XRTC_INT_STS_SECS_MASK) == (u32)0)) {
			/* Give the previous written time */
			CurrTime = XRtcPsu_GetLastSetTime(InstancePtr) - (u32)1;
		} else {
			/* Clear TimeUpdated */
			if ((InstancePtr->TimeUpdated == (u32)1) &&
			    ((Status & XRTC_INT_STS_SECS_MASK) == (u32)1)) {
				InstancePtr->TimeUpdated = (u32)0;
			}

			/* RTC time got updated */
			CurrTime = XRtcPsu_ReadCurrentTime(InstancePtr);
		}
	} else {
		/* We come here if interrupts are enabled */
		if ((InstancePtr->TimeUpdated == (u32)1) &&
		    (InstancePtr->CurrTimeUpdated == (u32)0)) {
			/* Give the previous written time -1 sec */
			CurrTime = XRtcPsu_GetLastSetTime(InstancePtr) - (u32)1;
		} else {
			/* Clear TimeUpdated */
			if (InstancePtr->TimeUpdated == (u32)1) {
				InstancePtr->TimeUpdated = (u32)0;
			}
			/* RTC time got updated */
			CurrTime = XRtcPsu_ReadCurrentTime(InstancePtr);
		}
	}
	return CurrTime;
}

/****************************************************************************/
/**
 *
 * This function sets the alarm value of RTC device.
 *
 * @param	InstancePtr is a pointer to the XRtcPsu instance
 * @param	Alarm is the desired alarm time for RTC.
 * @param	Periodic says whether the alarm need to set at periodic
 *		Intervals or a one-time alarm.
 *
 * @return	None.
 *
 * @note		None.
 *
 *****************************************************************************/
void XRtcPsu_SetAlarm(XRtcPsu *InstancePtr, u32 Alarm, u32 Periodic)
{
	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(Alarm != 0U);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
	Xil_AssertVoid((Alarm - XRtcPsu_GetCurrentTime(InstancePtr)) > (u32)0);

	XRtcPsu_WriteReg(InstancePtr->RtcConfig.BaseAddr + XRTC_ALRM_OFFSET,
			 Alarm);
	if (Periodic != 0U) {
		InstancePtr->IsPeriodicAlarm = 1U;
		InstancePtr->PeriodicAlarmTime =
			Alarm - XRtcPsu_GetCurrentTime(InstancePtr);
	}
}


/****************************************************************************/
/**
 *
 *	This function translates time in seconds to a YEAR:MON:DAY HR:MIN:SEC
 *	format and saves it in the DT structure variable.
 *	It also reports the weekday.
 *
 * @param	Seconds is the time value that has to be shown in DateTime
 *		format.
 * @param	dt is the DateTime format variable that stores the translated
 *		time.
 *
 * @return	None.
 *
 * @note	This API supports this century i.e., 2000 - 2099 years only.
 *
 *****************************************************************************/
void XRtcPsu_SecToDateTime(u32 Seconds, XRtcPsu_DT *dt)
{
	u32 CurrentTime;
	u32 TempDays;
	u32 DaysPerMonth;
	u32 Leap = 0U;

	CurrentTime = Seconds;
	dt->Sec = CurrentTime % 60U;
	CurrentTime /= 60U;
	dt->Min = CurrentTime % 60U;
	CurrentTime /= 60U;
	dt->Hour = CurrentTime % 24U;
	TempDays = CurrentTime / 24U;

	if (TempDays == 0U) {
		TempDays = 1U;
	}
	dt->WeekDay = TempDays % 7U;

	for (dt->Year = 0U; dt->Year <= 99U; ++(dt->Year)) {
		if ((dt->Year % 4U) == 0U) {
			Leap = 1U;
		} else {
			Leap = 0U;
		}

		if (TempDays <= (365U + Leap)) {
			break;
		}

		TempDays -= (365U + Leap);
	}

	for (dt->Month = 1U; dt->Month >= 1U; ++(dt->Month)) {
		DaysPerMonth = DaysInMonth[dt->Month - (u32)1];
		if ((Leap == 1U) && (dt->Month == 2U)) {
			DaysPerMonth++;
		}
		if (TempDays <= DaysPerMonth) {
			break;
		}

		TempDays -= DaysPerMonth;
	}

	dt->Day = TempDays;
	dt->Year += 2000U;
}

/****************************************************************************/
/**
 *
 * This function translates time in YEAR:MON:DAY HR:MIN:SEC format to
 * seconds.
 *
 * @param	dt is a pointer to a DatetTime format structure variable
 *		of time that has to be shown in seconds.
 *
 * @return	Seconds value of provided in dt time.
 *
 * @note		None.
 *
 *****************************************************************************/
u32 XRtcPsu_DateTimeToSec(XRtcPsu_DT *dt)
{
	u32 i;
	u32 Days;
	u32 Seconds;

	Xil_AssertNonvoid(dt != NULL);

	if (dt->Year >= 2000U) {
		dt->Year -= 2000U;
	}

	for (i = 1U; i < dt->Month; i++) {
		dt->Day += (u32)DaysInMonth[i - (u32)1];
	}
	if ((dt->Month > 2U) && ((dt->Year % 4U) == 0U)) {
		dt->Day++;
	}
	Days = dt->Day + (365U * dt->Year) + ((dt->Year + 3U) / 4U);
	Seconds = (((((Days * 24U) + dt->Hour) * 60U) + dt->Min) * 60U)
		  + dt->Sec;
	return Seconds;
}

/****************************************************************************/
/**
 *
 * This function calculates the calibration value depending on the actual
 * realworld time and also helps in deriving new calibration value if
 * the user wishes to change his oscillator frequency.TimeReal is generally the
 * internet time with EPOCH time as reference i.e.,1/1/1970 1st second.
 * But this RTC driver assumes start time from 1/1/2000 1st second. Hence,if
 * the user maps the internet time InternetTimeInSecs, then he has to use
 * XRtcPsu_SecToDateTime(InternetTimeInSecs,&InternetTime),
 * TimeReal = XRtcPsu_DateTimeToSec(InternetTime)
 * consecutively to arrive at TimeReal value.
 *
 * @param	InstancePtr is a pointer to the XRtcPsu instance.
 * @param	TimeReal is the actual realworld time generally an
 *		network time / Internet time in seconds.
 *
 * @param	CrystalOscFreq is the Oscillator new frequency.
 *		Say, If the user is going with the typical
 *		32768Hz, then he inputs the same
 *		frequency value.
 *
 * @return	None.
 *
 * @note		After Calculating the calibration register,
 *			user / application has to
 *			call again CfgInitialize API to bring the
 *			new calibration into effect.
 *
 *****************************************************************************/
void XRtcPsu_CalculateCalibration(XRtcPsu *InstancePtr, u32 TimeReal,
				  u32 CrystalOscFreq)
{
	u32 ReadTime;
	u32 SetTime;
	u32 Cprev;
	u32 Fprev;
	u32 Cnew;
	u32 Fnew;
	u32 Calibration;

	Xil_AssertVoid(TimeReal != 0U);

	Xil_AssertVoid(CrystalOscFreq != 0U);

	ReadTime = XRtcPsu_GetCurrentTime(InstancePtr);
	SetTime = XRtcPsu_GetLastSetTime(InstancePtr);
	Calibration = XRtcPsu_GetCalibration(InstancePtr);
	/*
	 * When board gets reseted, Calibration value is zero
	 * and Last setTime will be marked as 1st  second. This implies
	 * CurrentTime to be in few seconds say something in tens. TimeReal will
	 * be huge, say something in thousands. So to prevent
	 * such reset case, Cnew
	 * and Fnew will not be calculated.
	 */
	if ((Calibration == 0U) ||
	    (CrystalOscFreq != InstancePtr->OscillatorFreq)) {
		Cnew = CrystalOscFreq - (u32)1;
		Fnew = 0U;
	} else {
		float Xf;
		Cprev = Calibration & XRTC_CALIB_RD_MAX_TCK_MASK;
		Fprev = (Calibration & XRTC_CALIB_RD_FRACTN_DATA_MASK) >>
			XRTC_CALIB_RD_FRACTN_DATA_SHIFT;

		Xf = (float)((ReadTime - SetTime) / (TimeReal - SetTime));
		Xf = Xf * (float)((Cprev + 1U) + ((Fprev + 1U) / 16U));

		Cnew = (u32)(Xf) - (u32)1;
		Fnew = XRtcPsu_RoundOff((Xf - (u32)Xf) * (float)16U) - (u32)1;
	}

	Calibration = (Fnew << XRTC_CALIB_RD_FRACTN_DATA_SHIFT) + Cnew;
	Calibration |= XRTC_CALIB_RD_FRACTN_EN_MASK;

	InstancePtr->CalibrationValue = Calibration;
	InstancePtr->OscillatorFreq = CrystalOscFreq;
}

/****************************************************************************/
/**
 *
 * This function returns the seconds event status by reading
 * interrupt status register.
 *
 * @param	InstancePtr is a pointer to the XRtcPsu instance.
 *
 * @return	Returns 1 if a new second event is generated.Else 0..
 *
 * @note		This API is used in polled mode operation of RTC.
 *			This also clears interrupt status seconds bit.
 *
 *****************************************************************************/
u32 XRtcPsu_IsSecondsEventGenerated(XRtcPsu *InstancePtr)
{
	u32 Status;

	/* Loop the interrupt status register for Seconds Event */
	if ((XRtcPsu_ReadReg(InstancePtr->RtcConfig.BaseAddr +
			     XRTC_INT_STS_OFFSET) & (XRTC_INT_STS_SECS_MASK)) == 0U) {
		Status = 0U;
	} else {
		/* Clear the interrupt status register */
		XRtcPsu_WriteReg((InstancePtr)->RtcConfig.BaseAddr +
				 XRTC_INT_STS_OFFSET, XRTC_INT_STS_SECS_MASK);
		Status = 1U;
	}
	return Status;
}

/****************************************************************************/
/**
 *
 * This function returns the alarm event status by reading
 * interrupt status register.
 *
 * @param	InstancePtr is a pointer to the XRtcPsu instance.
 *
 * @return	Returns 1 if the alarm event is generated.Else 0.
 *
 * @note	This API is used in polled mode operation of RTC.
 *		This also clears interrupt status alarm bit.
 *
 *****************************************************************************/
u32 XRtcPsu_IsAlarmEventGenerated(XRtcPsu *InstancePtr)
{
	u32 Status;

	/* Loop the interrupt status register for Alarm Event */
	if ((XRtcPsu_ReadReg(InstancePtr->RtcConfig.BaseAddr +
			     XRTC_INT_STS_OFFSET) & (XRTC_INT_STS_ALRM_MASK)) == 0U) {
		Status = 0U;
	} else {
		/* Clear the interrupt status register */
		XRtcPsu_WriteReg((InstancePtr)->RtcConfig.BaseAddr +
				 XRTC_INT_STS_OFFSET, XRTC_INT_STS_ALRM_MASK);
		Status = 1U;
	}
	return Status;
}
/** @} */
